/* stitch.c
 * This file is part of Landmerge (http://code.google.com/p/landmerge/)
 * Copyright (c) Matus Tejiscak 2008
 * Covered by GNU GPL v3 (http://www.gnu.org/licenses/gpl.txt).
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef unsigned char uchar;

// Rectangle type.
typedef struct rect_t
{
	int x, y, w, h;
}
rect_t;

// An input (read-only) RGB file.
typedef struct rgb_file
{
	FILE *f;
	rect_t pos;
	uchar *scanline;
}
rgb_file;

#define NEW(type) ((type *) malloc(sizeof(type)))

// Parses a rect from its string representation.
rect_t parse_rect(const char *def)
{
	rect_t result;
	int matches = sscanf(def, "%dx%d@%d,%d", &result.w, &result.h, &result.x, &result.y);
	if (matches != 4)
	{
		fprintf(stderr, "Error parsing rect definition: %s\n", def);
		exit(1);
	}
	return result;
}

// Seek to the given scanline.
void rgb_seek(rgb_file *f, int y)
{
	if (y < 0 || y >= f->pos.h)
	{
		// We want pure black if the y-coord is invalid
		memset(f->scanline, 0, 3*f->pos.w);
	}
	else
	{
		fseek(f->f, y*3*f->pos.w, SEEK_SET);
		fread(f->scanline, 3*f->pos.w, 1, f->f);
	}
}

// Opens a RGB file for reading.
rgb_file *rgb_open(const char *fname, rect_t pos)
{
	rgb_file *result = NEW(rgb_file);
	if (!result)
	{
		fprintf(stderr, "%s: not enough memory\n", __func__);
		exit(1);
	}

	result->scanline = (uchar *) malloc(3*pos.w);
	result->f = fopen(fname, "rb");
	result->pos = pos;

	if (!result->f)
	{
		fprintf(stderr, "Could not open %s for reading.\n", fname);
		exit(1);
	}

	return result;
}

// Closes a RGB file.
void rgb_close(rgb_file *rgb)
{
	fclose(rgb->f);
	free(rgb->scanline);
}

// Opens multiple RGBs. Returns a NULL-terminated list of files.
rgb_file **open_input_rgbs(int count, char **defs)
{
	rgb_file **result = (rgb_file **) malloc((count+1) * sizeof(rgb_file *));
	if (!result)
	{
		fprintf(stderr, "%s: not enough memory.\n", __func__);
		exit(1);
	}

	for (int i = 0; i < count; ++i)
	{
		result[i] = rgb_open(defs[2*i+1], parse_rect(defs[2*i]));
	}
	result[count] = NULL;

	return result;
}

// Closes multiple RGBs.
void close_input_rgbs(rgb_file **rgbs)
{
	for (rgb_file **f = rgbs; *f; ++f)
	{
		free(*f);
	}
	free(rgbs);
}

// Prints usage.
void usage()
{
	const char *msg =
		"Usage: ./stitch outfile.rgb width height [infile infile ..]\n"
		"  where infile definition is:\n"
		"  1024x768@100,200 file.rgb    -- file.rgb: dimensions@placement\n"
		;
	fprintf(stderr, "%s", msg);
}

// Main stitching function.
void stitch(int width, int height, FILE *out, rgb_file **rgbs)
{
	// Allocate the output scanline
	uchar *scanline = (uchar *) malloc(3*width);

	for (int y = 0; y < height; ++y)
	{
		// Seek in the input files
		for (rgb_file **rgb = rgbs; *rgb; ++rgb)
		{
			rgb_file *img = *rgb;
			int yofs = y - img->pos.y;
			if (yofs < 0 || yofs >= img->pos.h) continue;
			rgb_seek(img, yofs);
		}

		for (int x = 0; x < width; ++x)
		{
			int sumR = 0, sumG = 0, sumB = 0, n = 0;

			// Sum the pixel value over all inputs
			for (rgb_file **rgb = rgbs; *rgb; ++rgb)
			{
				rgb_file *img = *rgb;
				int xofs = x - img->pos.x;
				int yofs = y - img->pos.y;

				uchar *p = img->scanline + 3*xofs;

				// Check bounds
				if (
					   xofs < 0 || xofs >= img->pos.w
					|| yofs < 0 || yofs >= img->pos.h
					|| (p[0]+p[1]+p[2] == 0)
				) continue;

				sumR += p[0];
				sumG += p[1];
				sumB += p[2];
				++n;
			}

			// Calculate the average
			if (n != 0)
			{
				sumR /= n;
				sumG /= n;
				sumB /= n;
			}

			// Put the pixel
			uchar *pixel = scanline + 3*x;
			pixel[0] = sumR;
			pixel[1] = sumG;
			pixel[2] = sumB;
		}

		fwrite(scanline, 3*width, 1, out);
	}

	free(scanline);
}

int main(int argc, char *argv[])
{
	// Check args
	if (argc < 6)
	{
		usage();
		exit(1);
	}

	const char *outfn = argv[1];
	int width = atoi(argv[2]);
	int height = atoi(argv[3]);

	// Open the files
	FILE *outf = fopen(outfn, "wb");
	if (!outf)
	{
		fprintf(stderr, "Could not open %s for writing.\n", outfn);
		exit(1);
	}
	rgb_file **rgbs = open_input_rgbs((argc - 4) / 2, argv + 4);

	stitch(width, height, outf, rgbs);

	// Close the files & clean up
	fclose(outf);
	close_input_rgbs(rgbs);

	return 0;
}
